﻿using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Afru;
using AfruPhone.Helper_Classes;

namespace Afru
{
    public class Engine
    {
        #region Variables

        #region Private variables

        List<GameScreen> gameScreens = new List<GameScreen>();
        Dictionary<string, Component> reusableComponents = new Dictionary<string, Component>();

        GameTime gameTime = null;

        ContentManager content = null;

        GraphicsDevice graphicsDevice = null;

        SpriteBatch spriteBatch = null;

        ServiceContainer services = null;

        Messaging messaging = null;

        //In order to calculate fps
        int _total_frames = 0;
        float _elapsed_time = 0.0f;
        int _fps = 0;

        //screenshot stuff
        bool _allowScreenshots;
        ScreenshotManager _screenshotManager = null;
        //KeyboardManager _screenKeys = null;
        RenderTarget2D _screenRender = null;

        //To be used for exiting
        Game _game;

        #endregion

        #region Public variables

        public Camera2D Camera; 
        // TODO Add support for multiple cameras

        public SpriteFont Font;

        public Vector2 ScreenDimensions { get { return new Vector2(graphicsDevice.PresentationParameters.BackBufferWidth, graphicsDevice.PresentationParameters.BackBufferHeight); } }

        public GameTime GameTime { get { return gameTime; } }

        public ContentManager Content { get { return content; } }

        public GraphicsDevice GraphicsDevice { get { return graphicsDevice; } }

        public SpriteBatch SpriteBatch { get { return spriteBatch; } }

        public ServiceContainer Services { get { return services; } }

        public Messaging Messaging { get { return messaging; } }

        public bool AllowScreenshots { get { return _allowScreenshots; } set { _allowScreenshots = value; } }

        /// <summary>
        /// Used to allow you to return to rendering to this in your components.
        /// Anything not rendered to this (ie. if you use Device.SetRenderTarget(null))
        /// will not be seen in screenshots.
        /// </summary>
        public RenderTarget2D MainRenderTarget { get { return _screenRender; } }

        public float Fps { get { return _fps; } }

        #endregion

        #endregion

        #region Constructor

        public Engine(GraphicsDeviceManager Graphics, Game game, ContentManager contentManager)
        {
            services = new ServiceContainer();
            services.AddService(typeof(IGraphicsDeviceService), Graphics);
            services.AddService(typeof(IGraphicsDeviceManager), Graphics);

            services.GetService(typeof(GraphicsDevice));

            this.graphicsDevice = Graphics.GraphicsDevice;

            content = contentManager;

            spriteBatch = new SpriteBatch(GraphicsDevice);

            this.graphicsDevice = GraphicsDevice;

            //ScreenDimensions = new Vector2(Graphics.PreferredBackBufferWidth, Graphics.PreferredBackBufferHeight);

            Camera = new Camera2D(ScreenDimensions);

            messaging = new Afru.Messaging();

            //_screenKeys = new KeyboardManager();

            _screenRender = new RenderTarget2D(graphicsDevice, graphicsDevice.PresentationParameters.BackBufferWidth, graphicsDevice.PresentationParameters.BackBufferHeight);
            
            _game = game;
        }

        #endregion

        #region Functions relating to gamescreens

        
        public void PushGameScreen(GameScreen GameScreen)
        {
            // Only allow GameScreens to exist in one Engine at a time
            if (GameScreen.Engine != null)
                throw new Exception("This GameScreen already exists on the stack of another Engine instance");

            if (!gameScreens.Contains(GameScreen))
            {
                if (gameScreens.Count > 0)
                {
                    //Transition the current screen off
                    gameScreens[gameScreens.Count - 1].CurrentScreenState = GameScreen.ScreenState.TransitionOff;
                    gameScreens[gameScreens.Count - 1].Visible = false;
                    gameScreens[gameScreens.Count - 1].Active = false;
                }
                
                //Add the new Screen
                GameScreen.engine = this;
                gameScreens.Add(GameScreen);
                GameScreen.LoadGameScreen();
                GameScreen.OnEnter();

                //Transition the new screen on
                gameScreens[gameScreens.Count - 1].CurrentScreenState = GameScreen.ScreenState.TransitionOn;     
            }
        }

        public void PushPopupScreen(GameScreen GameScreen, bool Pause)
        {
            // Only allow GameScreens to exist in one Engine at a time
            if (GameScreen.Engine != null)
                throw new Exception("This GameScreen already exists on the stack " +
                    " of another Engine instance");

            if (!gameScreens.Contains(GameScreen))
            {
                if (Pause && gameScreens.Count > 0)
                    gameScreens[gameScreens.Count - 1].Active = false;

                GameScreen.engine = this;
                gameScreens.Add(GameScreen);
                GameScreen.LoadGameScreen();
                GameScreen.OnEnter();
            }
        }

        //TODO Complete PopGameScreen()
        public void PopGameScreen()
        {
            if (gameScreens.Count != 0)
            {
                gameScreens[gameScreens.Count - 1].OnExit();
                gameScreens[gameScreens.Count - 1].CurrentScreenState = GameScreen.ScreenState.Inactive;
                gameScreens[gameScreens.Count - 1].engine = null;
                gameScreens.RemoveAt(gameScreens.Count - 1);
                if (gameScreens.Count > 0)
                {
                    gameScreens[gameScreens.Count - 1].OnEnter();
                    gameScreens[gameScreens.Count - 1].Active = true;
                    gameScreens[gameScreens.Count - 1].Visible = true;
                }
            }
        }

        public void PopPopupGameScreen()
        {
            if (gameScreens.Count != 0)
            {
                // Stop linking the screen to this Engine instance before removing the screen
                gameScreens[gameScreens.Count - 1].OnExit();
                gameScreens[gameScreens.Count - 1].engine = null;
                gameScreens.RemoveAt(gameScreens.Count - 1);
                //TODO Checkout PopPopupGameScreen
                //gameScreens[gameScreens.Count - 1].OnEnter(); <-- Popups don't alter the state of the screen below them?
                gameScreens[gameScreens.Count - 1].Active = true;
            }
        }

        public void PopGameScreens(int Screens)
        {
            for (int i = 0; i < Screens; i++)
                PopGameScreen();
        }

        public void PopPopUpGameScreens(int Screens)
        {
            for (int i = 0; i < Screens; i++)
                PopPopupGameScreen();
        }

        #endregion

        #region Class functions

        /// <summary>
        /// Quits the game
        /// </summary>
        public void Exit()
        {
            _game.Exit();
        }

        /// <summary>
        /// Begin a new spritebatch with the defualt arguments [SpriteSortMode.Deferred, BlendState.NonPremultiplied, null, null, null, null, Camera.GetTransformationZ]
        /// </summary>
        public void BeginDefaultSpritebatch()
        {
            this.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, null, null, null, null, this.Camera.GetTransformationZ());
        }

        /// <summary>
        /// Begin a new spritebatch with the default arguments [SpriteSortMode.Deferred, BlendState.NonPremultiplied, null, null, null, null, this.Camera.GetTransformationZParallax(Parallax)]
        /// This will factor in a parralax factor to the drawing of the component.
        /// </summary>
        /// <param name="parallax"></param>
        public void BeginDefaultSpritebatch(Vector2 Parallax)
        {
            this.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, null, null, null, null, this.Camera.GetTransformationZParallax(Parallax));
        }

        #endregion

        #region Update and draw

        public void Update(GameTime GameTime)
        {
            this.gameTime = GameTime;

            #region Calculate frames per second

            // Update
            _elapsed_time += (float)gameTime.ElapsedGameTime.TotalMilliseconds;
 
            // 1 Second has passed
            if (_elapsed_time >= 1000.0f)
            {
                _fps = _total_frames;
                _total_frames = 0;
                _elapsed_time = 0;
            }

            #endregion

            //screenshot stuff:
            //_screenKeys.Update();
            //if (_screenKeys.IsKeyPressed(Microsoft.Xna.Framework.Input.Keys.P) && _allowScreenshots
            //    && _screenKeys.IsKeyHeld(Microsoft.Xna.Framework.Input.Keys.RightAlt))
            /*{
                if (_screenshotManager == null) //only instantiate it if we need it
                {
                    _screenshotManager = new ScreenshotManager();
                }
                _screenshotManager.TakeScreenshot(graphicsDevice,_screenRender);
            }
             */

            foreach (Component component in reusableComponents.Values)
                if (component.Active)
                {
                    component.Update();
                }

            List<GameScreen> copy = new List<GameScreen>();

            foreach (GameScreen screen in gameScreens)
                copy.Add(screen);

            foreach (GameScreen screen in copy)
                if (screen.Active)
                {
                    switch (screen.CurrentScreenState)
                    {
                        case GameScreen.ScreenState.Active:
                            screen.Update();
                            break;

                        case GameScreen.ScreenState.TransitionOff:
                            screen.UpdateTransitionOff();
                            break;

                        case GameScreen.ScreenState.TransitionOffToQuit:
                            screen.UpdateTransitionOff();
                            break;

                        case GameScreen.ScreenState.TransitionOn:
                            screen.UpdateTransitionOn();
                            break;
                    }
                }
        }

        public void Draw(GameTime GameTime, Color color)
        {
            this.gameTime = GameTime;

            //Frames per second
            _total_frames++;

            graphicsDevice.SetRenderTarget(_screenRender);

            graphicsDevice.Clear(color);

            foreach (GameScreen screen in gameScreens)
                if (screen.Visible)
                {
                    switch (screen.CurrentScreenState)
                    {
                        case GameScreen.ScreenState.Active:
                            screen.Draw();
                            break;

                        case GameScreen.ScreenState.TransitionOff:
                            screen.TransitionOff();
                            break;

                        case GameScreen.ScreenState.TransitionOffToQuit:
                            screen.TransitionOffToQuit();
                            break;

                        case GameScreen.ScreenState.TransitionOn:
                            screen.TransitionOn();
                            break;
                    }
                }

            foreach (Component component in reusableComponents.Values)
                if (component.Visible)
                {
                    component.Draw();
                }

            graphicsDevice.SetRenderTarget(null);

        }

        public void EndDraw()
        {
            spriteBatch.Begin();
            spriteBatch.Draw((Texture2D)_screenRender, _screenRender.Bounds, Color.White);
            spriteBatch.End();
        }

        #endregion

        #region Reusable component helpers

        public bool AddReusableComponent(string name, Component component)
        {
            if ((reusableComponents.ContainsKey(name)) || DuplicateReusable(component))
                return false;

            
            component.ParentEngine = this;
            component.LoadComponent();
            reusableComponents.Add(name, component);

            return true;
        }

        private bool DuplicateReusable(Component component)
        {
            foreach (string k in reusableComponents.Keys)
            {
                if (reusableComponents[k].GetType() == component.GetType())
                {
                    return true;
                }
            }
            return false;
        }

        public Component GetReusableComponent(string name)
        {
            if (reusableComponents.ContainsKey(name))
            {
                return reusableComponents[name];
            }
            return null;
        }

        public T GetReusableOfType<T>()
        {
            foreach (string k in reusableComponents.Keys)
            {
                if (reusableComponents[k].GetType() == typeof(T))
                {
                    return (T)Convert.ChangeType(reusableComponents[k], typeof(T), null);
                }
            }
            return default(T);
        }
        #endregion
    }
}
